package com.zzs.common.minio.service.impl;

import com.zzs.common.core.enums.ResponseCode;
import com.zzs.common.core.util.SnowflakeUtil;
import com.zzs.common.minio.config.ImageProperties;
import com.zzs.common.minio.entity.File;
import com.zzs.common.minio.service.MinioService;
import com.zzs.common.security.exception.ServiceException;
import io.minio.*;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * @author zzs
 * @date 2025/03/21
 */
@Service
public class MinioServiceImpl implements MinioService {
    @Autowired
    private MinioClient minioClient;

    @Value("${minio.endpoint}")
    private String endpoint;

    @Value("${minio.bucket}")
    private String bucketName;

    @Autowired
    private ImageProperties imageProperties;

    @Value("${minio.accessKey}")
    private String accessKey;

    @Value("${minio.secretKey}")
    private String secretKey;

    /**
     * 初始化桶
     */
    private void initBucket() throws Exception {
        boolean bucketExists = minioClient.bucketExists(BucketExistsArgs.builder()
                .bucket(bucketName)
                .build());
//        判断桶是否存在, 不存在就创建
        if (!bucketExists) {
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
        }
        // 存储策略: 允许所有人读取, 禁止所有人上传、修改、删除
//        这样, 在线访问文件的时候, 就不需要加上数字签名了
//        如果不设置公共读取, 那么, 访问文件就需要带上数字签名, 而且, 数字签名最长的有效期是7天
//        String policyJson = """
//                {
//                    "Version": "2012-10-17",
//                    "Statement": [
//                        {
//                            "Effect": "Allow",
//                            "Principal": { "AWS": ["*"] },
//                            "Action": ["s3:GetObject"],
//                            "Resource": ["arn:aws:s3:::%s/*"]
//                        },
//                        {
//                            "Effect": "Deny",
//                            "Principal": { "AWS": ["*"] },
//                            "Action": ["s3:PutObject", "s3:DeleteObject"],
//                            "Resource": ["arn:aws:s3:::%s/*"]
//                        }
//                    ]
//                }
//                """.formatted(bucketName, bucketName);

/*        第一条规则：
        允许所有用户读取对象（s3:GetObject）。
        第二条规则：
        允许 admin 用户（arn:aws:iam:::user/admin-access-key）删除对象（s3:DeleteObject）。
        第三条规则：
        禁止所有其他用户删除对象（s3:DeleteObject）。*/
        String policyJson = """
                {
                    "Version": "2012-10-17",
                    "Statement": [
                        {
                            "Effect": "Allow",
                            "Principal": { "AWS": ["*"] },
                            "Action": ["s3:GetObject"],
                            "Resource": ["arn:aws:s3:::%s/*"]
                        },
                        {
                            "Effect": "Allow",
                            "Principal": { "AWS": ["arn:aws:iam:::%s/%s"] },
                            "Action": ["s3:DeleteObject"],
                            "Resource": ["arn:aws:s3:::%s/*"]
                        },
                        {
                            "Effect": "Deny",
                            "Principal": { "AWS": ["*"] },
                            "Action": ["s3:DeleteObject"],
                            "Resource": ["arn:aws:s3:::%s/*"]
                        }
                    ]
                }
                """.formatted(bucketName, accessKey, secretKey, bucketName, bucketName);

        minioClient.setBucketPolicy(SetBucketPolicyArgs.builder()
                .bucket(bucketName)
                .config(policyJson)
                .build());
    }

    @PostConstruct
    public void init() throws Exception {
        initBucket();
    }

    @Override
    public File uploadImage(MultipartFile file) throws Exception {
        if (!imageProperties.getTypes().contains(file.getContentType())) {
            throw new ServiceException(ResponseCode.FILE_NO_MATCH);
        }
        if (file.getSize() > imageProperties.getMaxSize()) {
            throw new ServiceException(ResponseCode.FILE_TOO_LARGE);
        }
        String imageName = String.valueOf(SnowflakeUtil.nextId());
        String imageUrl = uploadFile(imageName, file);
        return new File(imageName, imageUrl);
    }

    @Override
    public void removeFile(String fileName) throws Exception {
        minioClient.removeObject(RemoveObjectArgs.builder()
                .bucket(bucketName)
                .object(fileName)
                .build());
    }

    @Override
    public void removeFiles(List<String> fileNameList) {
        List<DeleteObject> objects = new ArrayList<>(fileNameList.size());
        for (String s : fileNameList) {
            objects.add(new DeleteObject(s));
        }
        Iterable<Result<DeleteError>> results = minioClient.removeObjects(RemoveObjectsArgs.builder()
                .bucket(bucketName)
                .objects(objects)
                .build());

        // 遍历删除结果，检查是否有错误
        for (Result<DeleteError> result : results) {
            try {
                DeleteError error = result.get();
                System.err.println("删除失败：" + error.objectName() + "，错误：" + error.message());
            } catch (Exception e) {
                System.err.println("删除时发生异常：" + e.getMessage());
            }
        }
    }

    /**
     * 上传文件, 并返回该文件的url
     */
    private String uploadFile(String fileName, MultipartFile file) throws Exception {
        try (InputStream inputStream = file.getInputStream()) {
            minioClient.putObject(PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(fileName)
                    .stream(inputStream, file.getSize(), -1)
                    .contentType(file.getContentType())
                    .build());
        }
        return endpoint + "/" + bucketName + "/" + fileName;
    }
}
